﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class SearchNode
{
	public Tile tile = null;
	public float fScore = 0f;
	public float gScore = 0f;
	public SearchNode parent = null;
	
	public SearchNode(Tile _tile)
	{
		tile = _tile;
	}
}

public class Search
{
	public static List<Tile> GetPathZombie(Tile _start, Tile _end)
	{
		List<SearchNode> closedSet = new List<SearchNode>();
		List<SearchNode> openSet = new List<SearchNode>();
		openSet.Add(new SearchNode(_start));
		
		SearchNode current = null;
		SearchNode neighbour = null;
		while (openSet.Count != 0)
		{
			current = LowestF(openSet);
			if (current.tile == _end)
			{
				return BuildPath(current);
			}
			
			openSet.Remove(current);
			closedSet.Add(current);
			
			foreach (Tile neighbourTile in current.tile.neighbours)
			{
				if (neighbourTile.objectOnTop == null || !(neighbourTile.objectOnTop is Character) || neighbourTile == _end)
				{
					neighbour = FindInList(closedSet, neighbourTile);
					if (neighbour == null)
					{
						bool inOpenSet = true;
						neighbour = FindInList(openSet, neighbourTile);
						if (neighbour == null)
						{
							inOpenSet = false;
							neighbour = new SearchNode(neighbourTile);
						}
						
						float tempGScore = current.gScore + DistanceValue(current, neighbour) + ManhattanValue(current, neighbour);
						if (!inOpenSet || tempGScore < neighbour.gScore)
						{
							neighbour.parent = current;
							neighbour.gScore = tempGScore;
							neighbour.fScore = Vector3.SqrMagnitude(_end.transform.position - neighbourTile.transform.position) + tempGScore;
							if (!inOpenSet)
							{
								openSet.Add(neighbour);
							}
						}
					}
				}
			}
		}
		
		return null;
	}
	
	public static List<Tile> GetPath(Tile _start, Tile _end, List<Tile> _area, uint _check = 0)
	{
		List<SearchNode> closedSet = new List<SearchNode>();
		List<SearchNode> openSet = new List<SearchNode>();
		openSet.Add(new SearchNode(_start));
		
		SearchNode current = null;
		SearchNode neighbour = null;
		while (openSet.Count != 0)
		{
			current = LowestF(openSet);
			if (current.tile == _end)
			{
				return BuildPath(current);
			}
			
			openSet.Remove(current);
			closedSet.Add(current);
			
			foreach (Tile neighbourTile in current.tile.neighbours)
			{
				if ((_area == null || _area.Contains(neighbourTile)) || neighbourTile == _end
				    && Area.RespectsConditions(current.tile, neighbourTile, _check))
				{
					neighbour = FindInList(closedSet, neighbourTile);
					if (neighbour == null)
					{
						bool inOpenSet = true;
						neighbour = FindInList(openSet, neighbourTile);
						if (neighbour == null)
						{
							inOpenSet = false;
							neighbour = new SearchNode(neighbourTile);
						}
						
						float tempGScore = current.gScore + DistanceValue(current, neighbour) + ManhattanValue(current, neighbour);
						if (!inOpenSet || tempGScore < neighbour.gScore)
						{
							neighbour.parent = current;
							neighbour.gScore = tempGScore;
							neighbour.fScore = Vector3.SqrMagnitude(_end.transform.position - neighbourTile.transform.position) + tempGScore;
							if (!inOpenSet)
							{
								openSet.Add(neighbour);
							}
						}
					}
				}
			}
		}
		
		return null;
	}

	private static List<Tile> BuildPath(SearchNode _last)
	{
		List<Tile> path = new List<Tile>();
		SearchNode current = _last;
		while (current != null)
		{
			path.Add(current.tile);
			current = current.parent;
		}

		path.Reverse();
		return path;
	}

	private static SearchNode FindInList(List<SearchNode> _nodes, Tile _tile)
	{
		foreach (SearchNode node in _nodes)
		{
			if (node.tile == _tile)
			{
				return node;
			}
		}

		return null;
	}

	private static SearchNode LowestF(List<SearchNode> _openSet)
	{
		float lowestValue = Mathf.Infinity;
		SearchNode lowestNode = null;
		foreach (SearchNode node in _openSet)
		{
			if (node.fScore < lowestValue)
			{
				lowestValue = node.fScore;
				lowestNode = node;
			}
		}

		return lowestNode;
	}

	private static float DistanceValue(SearchNode a, SearchNode b)
	{
		return Vector3.Distance(a.tile.transform.position, b.tile.transform.position);
	}

	private static float ManhattanValue(SearchNode a, SearchNode b)
	{
		if (a.parent == null)
		{
			return 0f;
		}

		float angle1 = Vector3.Angle(Vector3.up, a.tile.transform.position - b.tile.transform.position);
		float angle2 = Vector3.Angle(Vector3.up, a.parent.tile.transform.position - a.tile.transform.position);

		return Mathf.Abs((angle1 - angle2));
	}
}
